Qdrant: ベクトル検索入門チュートリアル

Qdrantのチュートリアルへようこそ。Qdrantは、オープンソースのベクトルデータベース兼ベクトル類似性検索エンジンです。ベクトル埋め込みを保存、検索、管理する強力で本番稼働可能なサービスを提供し、次世代のAI駆動アプリケーションの構築を支援します。

このノートブックでは、Qdrantの基本概念をカバーし、シンプルなセマンティック検索エンジンを構築する実践的な例を追っていきます。以下を学びます:

  1. セットアップとインストール: 環境を準備する。
  2. Qdrantへの接続: Qdrantクライアントを初期化する。
  3. コレクションの作成: ベクトル用の空間を設定する。
  4. ベクトルの生成とアップロード: ベクトルデータを準備して保存する。
  5. 検索の実行: セマンティック検索とフィルタ検索を行う。

1. セットアップとインストール

まず、必要なPythonライブラリをインストールする必要があります。Qdrantとのやり取りにはqdrant-client、ベクトル埋め込みの生成にはsentence-transformers、APIキーと環境変数の管理にはpython-dotenvを使用します。

ターミナルで以下のコマンドを実行してインストールできます。

#pip install qdrant-client sentence-transformers python-dotenv -q

環境変数の管理

APIキーなどの機密情報を安全に管理するのがベストプラクティスです。.envファイルを使ってこれらの認証情報を管理します。このノートブックと同じディレクトリに.envという名前のファイルを作成し、以下の行を追加してください。

QDRANT_API_KEY="your_qdrant_api_key_here"

QDRANT_URL="your_qdrant_url_here"

このチュートリアルでは、簡単にQdrant Cloudクラスタをセットアップして上記の環境変数を取得できます。

import os
from dotenv import load_dotenv
from qdrant_client import QdrantClient, models
from sentence_transformers import SentenceTransformer

# Load environment variables from .env file
load_dotenv()

# Get the API key and URL from environment variables
QDRANT_API_KEY = os.getenv("QDRANT_API_KEY")
QDRANT_URL = os.getenv("QDRANT_URL")

2. Qdrantへの接続

QdrantClientはQdrantサービスとのやり取りの入り口です。Qdrantは複数の方法で実行できます。

  • インメモリ: プロセス終了時にデータが消えるため、素早い実験やテストに最適です。
  • ディスク上ストレージ: ローカルで永続化されるストレージオプションです。
  • Docker: Qdrantをスタンドアロンサーバーとして実行します。
  • Qdrant Cloud: フルマネージドでスケーラブルなクラウドソリューションです。

簡単のため、このチュートリアルではQdrant Cloudを使用します。

# Initialize the Qdrant client for in-memory storage
# client = QdrantClient(":memory:")

# If you were connecting to Qdrant Cloud, you would use:
client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY)

3. コレクションの作成

Qdrantでは、コレクションはポイント(ペイロード付きベクトル)の名前付きセットです。SQLデータベースのテーブルと考えてください。コレクションを作成するときは、そこに保存するベクトルの設定、たとえばサイズ(次元数)や類似性検索用の距離メトリックを定義する必要があります。

一般的な距離メトリックには以下があります。

  • Cosine: 2つのベクトル間の角度を測ります。テキスト埋め込みに最適です。
  • Euclidean: 2点間の直線距離です。
  • Dot Product: ベクトルの大きさも考慮した類似性の指標です。
my_collection = "my_first_collection"

client.recreate_collection(
    collection_name=my_collection,
    vectors_config=models.VectorParams(size=384, distance=models.Distance.COSINE),
)
/var/folders/pv/g_b0j0n53rz5fm8yrlw3jg040000gn/T/ipykernel_48560/1209000986.py:3: DeprecationWarning: `recreate_collection` method is deprecated and will be removed in the future. Use `collection_exists` to check collection existence and `create_collection` instead.
  client.recreate_collection(
True

4. ベクトルの生成とアップロード

セマンティック検索を行うには、テキストデータをベクトル埋め込みと呼ばれる数値表現に変換する必要があります。このタスクにはsentence-transformersの事前学習済みモデルを使用します。モデルall-MiniLM-L6-v2は速度と品質のバランスが良く、サイズ384のベクトルを出力するため、コレクションの設定と一致します。

# Load a pre-trained sentence transformer model
model = SentenceTransformer('all-MiniLM-L6-v2')

# Let's create some sample documents
documents = [
    {"id": 1, "text": "Qdrant is a vector database for building AI applications.", "metadata": {"type": "tech"}},
    {"id": 2, "text": "The Eiffel Tower is a famous landmark in Paris, France.", "metadata": {"type": "travel"}},
    {"id": 3, "text": "A vector database indexes vectors for easy search and retrieval.", "metadata": {"type": "tech"}},
    {"id": 4, "text": "The Great Wall of China is one of the world's wonders.", "metadata": {"type": "travel"}},
    {"id": 5, "text": "Artificial intelligence is transforming many industries.", "metadata": {"type": "tech"}}
]

# Generate embeddings for our documents
embeddings = model.encode([doc["text"] for doc in documents])
/Users/devon/.pyenv/versions/3.10.14/lib/python3.10/site-packages/torch/nn/modules/module.py:1520: FutureWarning: `encoder_attention_mask` is deprecated and will be removed in version 4.55.0 for `BertSdpaSelfAttention.forward`.
  return forward_call(*args, **kwargs)

ポイントのアップサート

埋め込みができたので、Qdrantコレクションにアップロードします。Qdrantでは、ポイントはベクトル、一意のID、オプションのペイロード(メタデータ用のJSONオブジェクト)からなる中心的なエンティティです。同じIDを持つ既存のポイントを追加または更新するupsert操作を使用します。

client.upsert(
    collection_name=my_collection,
    points=[
        models.PointStruct(
            id=doc["id"],
            vector=embedding.tolist(),
            payload=doc["metadata"]
        )
        for doc, embedding in zip(documents, embeddings)
    ],
    wait=True,
)
UpdateResult(operation_id=0, status=<UpdateStatus.COMPLETED: 'completed'>)

5. 検索の実行

セマンティック検索

ベクトルデータベースの中核となる機能は、与えられたクエリベクトルに最も似たベクトルを見つけることです。これがセマンティック検索です。クエリを取り、同じモデルでベクトルにエンコードし、Qdrantで最も近い一致を見つけます。

query = "What is a vector database?"
query_vector = model.encode(query).tolist()

search_results = client.query_points(
    collection_name=my_collection,
    query=query_vector,
    limit=2,  # Return the top 2 most similar results
    with_payload=True,
)

# Map IDs to original document text so we can display it with results
id_to_text = {doc["id"]: doc["text"] for doc in documents}

print("Semantic Search Results:")
for result in search_results.points:
    print(f"- ID: {result.id}, Score: {result.score:.4f}")
    text = id_to_text.get(result.id)
    if text is not None:
        print(f"  Text: {text}")
Semantic Search Results:
- ID: 3, Score: 0.6311
  Text: A vector database indexes vectors for easy search and retrieval.
- ID: 1, Score: 0.5301
  Text: Qdrant is a vector database for building AI applications.

フィルタ付き検索

Qdrantの真の力は、ベクトル検索とペイロードに対するフィルタリングを組み合わせられることにあります。これにより、特定のメタデータ条件に一致するポイントのみに検索を絞り込めます。ここでは、同じクエリに対してtravelタイプのドキュメントのみを検索します。

# Create a payload index for the string field 'type' so we can filter on it
# Qdrant requires an index of type KEYWORD for exact-match string filters
try:
    client.create_payload_index(
        collection_name=my_collection,
        field_name="type",
        field_schema=models.PayloadSchemaType.KEYWORD,
    )
    print("Created payload index for 'type' (KEYWORD).")
except Exception as e:
    # Safe to ignore if already exists and we're re-running cells
    if "already exists" in str(e).lower():
        print("Payload index for 'type' already exists.")
    else:
        raise
Created payload index for 'type' (KEYWORD).
filtered_search_results = client.query_points(
    collection_name=my_collection,
    query=query_vector,
    query_filter=models.Filter(
        must=[
            models.FieldCondition(
                key="type",
                match=models.MatchValue(value="travel")
            )
        ]
    ),
    limit=2,
    with_payload=True,
)

print("Filtered Search Results (type='travel'):")
for result in filtered_search_results.points:
    print(f"- ID: {result.id}, Score: {result.score:.4f}, Payload: {result.payload}")
    text = id_to_text.get(result.id)
    if text is not None:
        print(f"  Text: {text}")
Filtered Search Results (type='travel'):
- ID: 2, Score: 0.0032, Payload: {'type': 'travel'}
  Text: The Eiffel Tower is a famous landmark in Paris, France.
- ID: 4, Score: -0.0419, Payload: {'type': 'travel'}
  Text: The Great Wall of China is one of the world's wonders.

まとめ

おめでとうございます!Qdrantを使って小さなセマンティック検索エンジンを構築できました。以下を学びました。

  • 環境をセットアップし、Qdrantに接続する。
  • 特定のベクトル設定でコレクションを作成する。
  • テキストデータからベクトル埋め込みを生成する。
  • ベクトルとメタデータペイロードを持つポイントをアップサートする。
  • セマンティック検索とフィルタ検索の両方を実行する。

これは始まりに過ぎません。ここからは、さらに高度なトピックを探索できます。

  • ハイブリッド検索: キーワードベース(スパース)ベクトルとセマンティック(デンス)ベクトルを組み合わせて、より正確な結果を得る。
  • スケーラビリティ: DockerやQdrant Cloudを使用して、より大規模な本番レベルのアプリケーションに対応する。
  • 高度なフィルタリング: より複雑なフィルタ条件を作成する。

Happy building!